package StorageComponents;
import ControlComponents.ProgramCounter;
import Exceptions.EmptyMemoryException;
import Exceptions.InvalidArrayLengthException;
import Exceptions.InvalidMemoryAllocationException;

/**
 * 
 * This simulates the microprocessor's memory. It has 256 address spaces that store 8 bits each.
 * This is done using using an Object type array of size 256, which stores integer type arrays of size 8.
 */
public class Memory {

	//this generic array of type Object represents every address,
	//and will store another array of integers that stores each bit
	private Object[] mem;
	//this counter keeps track of the the reads
	private int writeCount;
	
	/**
	 * Constructing a memory of 256 cells, where each can store 8 bits
	 */
	public Memory(){
		//main memory has 256 addressable spaces
		mem = new Object[256];
		for( int  i = 0; i < mem.length; i++ ){
			//8 bits in each address
			mem[i] = new int[8];
		}
		//no write operations have been executed
		writeCount = 0;
	}
	
	/**
	 * This method reads a single byte from memory, and returns it in an array of integers.
	 * It can only be called after at least a byte has been written in memory. This method throws
	 * an EmptyMemoryException of main memory has not been written at least once.
	 * @param address - index of byte to read from memory
	 * @return array of 8 integers (8 bits read from memory)
	 */
	public int[] readByte(int address) throws EmptyMemoryException, IndexOutOfBoundsException{
		//make sure at least one byte has been previously written
		if( isEmpty() )
			throw new EmptyMemoryException("Cannot perform read operation because no write operations +" +
					"have been executed. Main memory is empty");

		//this will throw an IndexOutOfBoundsException if address < 0 || address > 255
		//it's safe to cast element with index = address to an array of integers
		return ( int[] ) mem[address];
	}
	
	/**
	 * This method reads a two bytes from memory's code segment (bytes 0 - 127), and returns them 
	 * in an array of integers. To address these bytes, the program counter is used :)
	 * @param pc - ProgramCounter object
	 * @return array of 16 integers (1 instruction - 16 bits read from memory's code segment)
	 */
	public int[] readInstruction(int pc) throws EmptyMemoryException, IndexOutOfBoundsException{
		
		int[] instruction = new int[16], temp;
		//AR2RISC employs big endian; read first 8 bits
		temp = readByte(pc);
		//fill instruction register bits 8-15
		for( int i = 0; i < instruction.length/2; i++ )
				instruction[i] = temp[i];
		//read the next 8 bits
		pc++;
		temp = readByte(pc);
		//fill instruction register bits 0-7
		for( int i = instruction.length/2, j = 0; i < instruction.length; i++, j++ )
					instruction[i] = temp[j];

		return instruction;
	}
	
	
	/**
	 * This method stores a single byte in memory
	 * @param data - array of 8 integers which represent the 8 bits to be stored in memory
	 * @param address - the address of where to store the 8 bits
	 */
	public void writeByte(int[] data, int address)throws InvalidArrayLengthException, IndexOutOfBoundsException{
		if( data.length != 8 )
			throw new InvalidArrayLengthException("Cannot perform writeByte operation. Expected data.length = 8"+
					" and received data.length = " + data.length);
		//this will throw an IndexOutOfBoundsException if address < 0 || address > 255
		int[] memoryCell = (int[]) mem[address];
		for( int i = 0; i < data.length; i++ )
			memoryCell[i] = data[i];
		writeCount++;
	}
	
	
	/**
	 * This method stores 2 bytes in memory. If any data was stored at the address
	 * represented by the explicit parameter "address" and "address + 1", then it will be "over written in memory"
	 *  		
	 * @param data - array of 16 integers which represent the 16 bits to be stored in memory
	 * @param address - the address of where to store the 16 bits
	 */
	public void writeWord(int[] data, int address) throws InvalidArrayLengthException, IndexOutOfBoundsException,
		InvalidMemoryAllocationException{
		if( data.length != 16 )
			throw new InvalidArrayLengthException("Cannot perform writeByte operation. Expected data.length = 16"+
					" and received data.length = " + data.length);
		if( address % 2 != 0 )
			throw new InvalidMemoryAllocationException("Cannot perform writeByte operation. "+
					"Expected an even integer as an address and received address = " +address );
		
		int[] temp = new int[8];
		int[] temp2 = new int[8];
		//first byte
		for( int i = data.length - 1, j = 0; i >= data.length/2; i--, j++ )
			temp[j] = data[i];
		writeByte( temp, address );
		address++; //address second byte
		//second byte
		for( int i = data.length/2 - 1, j = 0; i >= 0; i--, j++ )
			temp2[j] = data[i];
		writeByte( temp2, address );
	}
	/**
	 * Check if memory cells are empty
	 * @return if empty
	 */
	public boolean isEmpty(){
			return writeCount == 0;
	}
	
}
